﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Aleator.Core.Techniques
{
    /// <summary>
    /// Vibing singleton
    /// </summary>
    public sealed class Vibe : Technique
    {
        #region Members

        private static readonly Vibe m_instance = new Vibe();
        private static readonly int m_drumsOn = Convert.ToInt32(Aleator.Resources.AppResources.DirtyKitOnMessageID);
        private static readonly int m_drumsOff = Convert.ToInt32(Aleator.Resources.AppResources.DirtyKitOffMessageID);

        private static int[] m_onedrops = { 2, 6, 10 };
        private static int[] m_rockers = { 0, 4, 8 };
        private static int[] m_steppers = { 1, 3, 5, 7, 9, 11 };

        #endregion

        #region Constructor

        private Vibe() { }

        #endregion

        #region Public Methods

        /// <summary>
        /// BounceGenerator instance
        /// </summary>
        public new static Vibe Instance
        {
            get
            {
                return m_instance;
            }
        }

        public int Location
        {
            get
            {
                return m_location;
            }
        }


        /// <summary>
        /// Fill phrase with notes
        /// </summary>
        /// <param name="p">Phrase we are going to fill</param>
        public override void Generate(ConcretePhrase p)
        {
            m_overrideNoteOn = 0;
            m_overrideNoteOff = 0;

            if (p.GetType() == typeof(MelodyPhrase))
                throw new Exception("Vibe tecnique not implemented for melody phrasing");
            //Only rock 4's and 6's
            else if (p.Progression.Numerator % 2 == 1)
                throw new Exception("Vibe technique not implemented for odd numerators");

            base.Generate(p);

            DrumPhrase dp = (DrumPhrase)p;
            dp.TechniqueType = TechniqueType.Vibe;

            //Calculate totals
            SetTotals(dp);

            //Figure out remainders for all lists
            if (dp.KickNotes.Length > dp.Progression.TotalMeasures)
                dp.KickMod = dp.KickNotes.Length % dp.Progression.Numerator;

            if (dp.SnareNotes.Length > dp.Progression.TotalMeasures)
                dp.SnareMod = dp.SnareNotes.Length % dp.Progression.Numerator;

            if (dp.HiHatNotes.Length > dp.Progression.TotalMeasures)
                dp.HighHatMod = dp.HiHatNotes.Length % dp.Progression.Numerator;

            BuildKick(dp);
            BuildSnare(dp);
            BuildHiHat(dp);
        }

        #endregion

        #region Private Methods

        private static void BuildKick(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            //Quantize the kick to 8ths (quaters / 2)
            dp.KickResolution = 2;

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int kicksInMeasure = dp.KickNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.KickResolution;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < kicksInMeasure)
            {
                m_instance.GetBeat(dp.Progression.Numerator, dp.KickResolution, j);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == 0).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * kicksInMeasure)
                    break;

                for (int k = 0; k < kicksInMeasure; k++)
                {
                    tmpNote = dp.KickNotes[j % kicksInMeasure];
                    dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.KickNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.KickNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.KickResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.KickNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.KickNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.KickNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.KickNum, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.KickNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildSnare(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            //Quantize the snare to 16ths (quarters / 4)
            dp.SnareResolution = 4;

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int snaresInMeasure = dp.SnareNotes.Length / dp.Progression.TotalMeasures;

            //Set the highest possible location given the meter and resolution
            int playablenotesinmeasure = dp.Progression.Numerator * dp.SnareResolution;

            //Decide if we are hitting the head or side of the snare
            int notenumber;

            if (Session.RndGenerator.Next(2) == 0)
                notenumber = DrumPhrase.SnareNum;
            else
                notenumber = DrumPhrase.SideStickNum;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < snaresInMeasure)
            {
                m_location = Session.RndGenerator.Next(dp.Progression.Numerator * dp.SnareResolution);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.SnareNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, notenumber, trueNoteOn, trueNoteOn, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * snaresInMeasure)
                    break;

                for (int k = 0; k < snaresInMeasure; k++)
                {
                    tmpNote = dp.SnareNotes[j % snaresInMeasure];
                    dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, notenumber, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.SnareNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.SnareNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.SnareResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.SnareNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.SnareNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.SnareNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, notenumber, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.SnareNotes[j]);
                        j++;
                    }
                }
            }
        }

        private static void BuildHiHat(DrumPhrase dp)
        {
            ConcreteNote tmpNote;

            //Quantize the snare to 16ths (quarters / 4)
            dp.HiHatResolution = 4;

            int trueNoteOn;
            int trueNoteOff;

            if (m_overrideNoteOn > 0)
            {
                trueNoteOn = m_overrideNoteOn;
                trueNoteOff = m_overrideNoteOff;
            }
            else
            {
                trueNoteOn = m_drumsOn;
                trueNoteOff = m_drumsOff;
            }

            //Determine how many kicks are in each measure without the remainder
            int highHatsInMeasure = dp.HiHatNotes.Length / dp.Progression.TotalMeasures;

            //inner counter will be re-used to add remaining kicks into the phrase below
            int j = 0;
            m_location = 0;

            //Fill out the first measure of the phrase
            while (j < highHatsInMeasure)
            {
                m_location = Session.RndGenerator.Next(dp.Progression.Numerator * dp.HiHatResolution);

                //If the number returned by the generator is within the desired range and has not already been selected,
                //We can add it it to the note array
                if (!dp.HiHatNotes.Where(k => k != null && k.Location == m_location).Any())
                {
                    //Create the new beat and add it to the kick array, passing in the beat, measure (i) and note numbers
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, 0, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Fill out the rest
            for (int i = 1; i < dp.Progression.TotalMeasures; i++)
            {
                if (j > dp.Progression.TotalMeasures * highHatsInMeasure)
                    break;

                for (int k = 0; k < highHatsInMeasure; k++)
                {
                    tmpNote = dp.HiHatNotes[j % highHatsInMeasure];
                    dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(tmpNote.Location, i, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                    dp.Notes.Add(dp.HiHatNotes[j]);
                    j++;
                }
            }

            //Get any beat that isn't in the kick collection yet
            List<int> beats = new List<int>();
            List<ConcreteNote> fullnotes = dp.HiHatNotes.Where(k => k != null).ToList();

            try
            {
                for (int i = 0; i < dp.Progression.Numerator * dp.HiHatResolution; i++)
                {
                    if (fullnotes.Where(n => n.Location == i).Count() == 0)
                        beats.Add(i);
                }
            }
            catch (NullReferenceException nre)
            {
                Debug.WriteLine("Error message: " + nre.Message);
            }

            if (beats.Any())
            {
                m_location = beats[Session.RndGenerator.Next(beats.Count)];

                //Add the remaining notes to the array to fill it up using the same process
                while (j < dp.HiHatNotes.Length)
                {
                    m_measure = Session.RndGenerator.Next(dp.Progression.TotalMeasures);

                    if (dp.HiHatNotes.Where(k => k != null && k.Location == m_location && k.Measure == m_measure).Count() == 0)
                    {
                        dp.HiHatNotes[j] = (ConcreteNote)m_notefactory.GetNote(m_location, m_measure, DrumPhrase.HiHatNum, trueNoteOn, trueNoteOff, dp);
                        dp.Notes.Add(dp.HiHatNotes[j]);
                        j++;
                    }
                }
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Set note totals
        /// </summary>
        /// <param name="p">Phrase we are filling</param>
        protected override void SetTotals(Phrase p)
        {
            DrumPhrase dp = (DrumPhrase)p;

            int nextkick = 0;
            int nextsnare = 0;
            int nexthighhat = 0;

            bool totalset = false;

            int totalonedrops = 2 * dp.Progression.TotalMeasures;
            int totalsteppers = 8 * dp.Progression.TotalMeasures;

            m_normal.Sigma = 0.5;

            //Set kick totals acording to specific rhythm
            if (dp.PhraseRhythm.RhythmName == "OneDrop")
                m_normal.Mu = (double)(dp.Progression.Numerator / 2);
            else if (dp.PhraseRhythm.RhythmName == "Steppers")
                m_normal.Mu = (double)dp.Progression.Numerator;
            else
                m_normal.Mu = (double)(dp.Progression.Numerator * 2);

            //We need at least 2 kicks per measure, no more than 8 per
            while (nextkick < totalonedrops || nextkick > totalsteppers)
                nextkick = (int)(Math.Round(m_normal.NextDouble()) * dp.Progression.TotalMeasures);

            //Set continuous bounds for snare total calculations
            m_continuous.Alpha = 0.0;
            m_continuous.Beta = (double)dp.Progression.TotalMeasures * 2;

            if (!totalset)
            {
                totalset = true;
                nextsnare = (int)Math.Round(m_continuous.NextDouble());
            }

            totalset = false;

            //Set continous bounds for high hat total calculations
            m_continuous.Beta = dp.Progression.TotalMeasures * 16;
            m_continuous.Alpha = dp.Progression.TotalMeasures * 8;

            if (!totalset)
            {
                totalset = true;
                nexthighhat = (int)Math.Round(m_continuous.NextDouble());
            }

            dp.KickNotes = new ConcreteNote[(int)nextkick];
            dp.SnareNotes = new ConcreteNote[(int)nextsnare];
            dp.HiHatNotes = new ConcreteNote[(int)nexthighhat];

            dp.Notes = new List<ConcreteNote>();
        }

        /// <summary>
        /// Get a randomized beat from the session's generator
        /// </summary>
        /// <param name="numerator"></param>
        /// <param name="res"></param>aram>
        public void GetBeat(int numerator, int res, int counter)
        /// <param name="counter"></p
        {
            if (counter < numerator / 2)
                m_location = m_onedrops[Session.RndGenerator.Next(numerator / 2)];
            else if (counter < numerator)
                m_location = m_rockers[Session.RndGenerator.Next(numerator / 2)];
            else
                m_location = m_steppers[Session.RndGenerator.Next(numerator)];
        }

        #endregion
    }
}
